import direct.directbase.DirectStart
from panda3d.core import *
from direct.gui.OnscreenText import OnscreenText
from direct.actor.Actor import Actor
from direct.showbase.DirectObject import DirectObject
import random, sys, os, math
from sounds import *

SPEED = 2.5

class World(DirectObject):

    def __init__(self):
        
        self.keyMap = {"leftA":0,"rightA":0,"forwardA":0,"brakeA":0,"fireA":0,
                       "leftB":0,"rightB":0,"forwardB":0,"brakeB":0,"fireB":0,
                       "leftC":0,"rightC":0,"forwardC":0,"brakeC":0,"fireC":0,
                       "leftD":0,"rightD":0,"forwardD":0,"brakeD":0,"fireD":0,
                       "cam-left":0, "cam-right":0}
        base.win.setClearColor(Vec4(0,0,0,1))


        #init sounds
        self.sounds = Sounds()
        self.sounds.playSong()
        
        
        # Set up the environment
        #
        # This environment model contains collision meshes.  If you look
        # in the egg file, you will see the following:
        #
        #    <Collide> { Polyset keep descend }
        #
        # This tag causes the following mesh to be converted to a collision
        # mesh -- a mesh which is optimized for collision, not rendering.
        # It also keeps the original mesh, so there are now two copies ---
        # one optimized for rendering, one for collisions.  

        self.environ = loader.loadModel("models/world")      
        self.environ.reparentTo(render)
        self.environ.setPos(0,0,0)
        
        # Create the main character, Ralph

        ralphStartPosA = self.environ.find("**/start_point").getPos()
        ralphStartPosB = self.environ.find("**/start_point").getPos()
        temp = list(ralphStartPosA)
        temp[1] -= 50
        ralphStartPosA = tuple(temp)
        
        self.ralphA = Actor("models/knight/car",
                                 {"accelerate":"models/knight/accelerate",
                                  "turn_left":"models/knight/turn_left",
                                  "turn_right":"models/knight/turn_right",
                                  "long_range":"models/knight/long_range",
                                  "death":"models/knight/death"})
        self.ralphA.reparentTo(render)
        self.ralphA.setScale(.1)
        self.ralphA.setPos(ralphStartPosA)
        
        self.ralphB = Actor("models/caveman/car",
                                 {"accelerate":"models/caveman/accelerate",
                                  "turn_left":"models/caveman/turn_left",
                                  "turn_right":"models/caveman/turn_right",
                                  "long_range":"models/caveman/long_range",
                                  "death":"models/caveman/death"})
        self.ralphB.reparentTo(render)
        self.ralphB.setScale(.1)
        self.ralphB.setPos(ralphStartPosB)
        
        self.ralphA.enableBlend()
        self.ralphB.enableBlend()
        #self.ralphA.setControlEffect("accelerate", 1)
        #self.ralphA.setControlEffect("turn_left", 1)
        #self.ralphA.setControlEffect("turn_right", 1)
        #self.ralphA.setControlEffect("long_range", 1)
        #self.ralphA.setControlEffect("death", 1)
        #self.ralphB.setControlEffect("accelerate", 1)
        #self.ralphB.setControlEffect("turn_left", 1)
        #self.ralphB.setControlEffect("turn_right", 1)
        #self.ralphB.setControlEffect("long_range", 1)
        #self.ralphB.setControlEffect("death", 1)
        
        self.ralphAHealth = 3
        self.ralphBHealth = 3

        # Create a floater object.  We use the "floater" as a temporary
        # variable in a variety of calculations.
        
        self.floaterA = NodePath(PandaNode("floater"))
        self.floaterA.reparentTo(render)
        
        self.floaterB = NodePath(PandaNode("floater"))
        self.floaterB.reparentTo(render)

        # Accept the control keys for movement and rotation

        self.accept("escape", sys.exit)
        self.accept("q", self.setKey, ["cam-left", 1])
        self.accept("e", self.setKey, ["cam-right", 1])
        
        self.accept("arrow_up", self.setKey, ["forwardA", 1])
        self.accept("arrow_left", self.setKey, ["leftA", 1])
        self.accept("arrow_right", self.setKey, ["rightA", 1])
        self.accept("arrow_down", self.setKey, ["brakeA",1])
        self.accept("p", self.setKey, ["fireA", 1])
        
        self.accept("w", self.setKey, ["forwardB", 1])
        self.accept("a", self.setKey, ["leftB", 1])
        self.accept("d", self.setKey, ["rightB", 1])
        self.accept("s", self.setKey, ["brakeB",1])
        self.accept("space", self.setKey, ["fireB", 1])
        
        #stop moving events
        self.accept("q-up", self.setKey, ["cam-left", 0])
        self.accept("e-up", self.setKey, ["cam-right", 0])
        
        self.accept("arrow_up-up", self.setKey, ["forwardA", 0])
        self.accept("arrow_left-up", self.setKey, ["leftA", 0])
        self.accept("arrow_right-up", self.setKey, ["rightA", 0])
        self.accept("arrow_down-up", self.setKey, ["brakeA",0])
        self.accept("p-up", self.setKey, ["fireA", 0])
        
        self.accept("w-up", self.setKey, ["forwardB", 0])
        self.accept("a-up", self.setKey, ["leftB", 0])
        self.accept("d-up", self.setKey, ["rightB", 0])
        self.accept("s-up", self.setKey, ["brakeB",0])
        self.accept("space-up", self.setKey, ["fireB", 0])
        
        self.accept("hit-bullet", self.hit)
        
        taskMgr.add(self.move,"moveTask")

        # Game state variables
        self.isMovingA = False
        self.isTurningA = False
        self.isMovingB = False
        self.isTurningB = False

        # Set up the camera
        
        base.disableMouse()
        base.camNode.setActive(False)
        
        self.cameraA = self.createCamera((0,.5,0,1))
        self.cameraB = self.createCamera((.5,1,0,1))
        
        self.cameraA.reparentTo(self.ralphA)
        self.cameraB.reparentTo(self.ralphB)
        
        self.cameraA.setPos(self.ralphA.getX(),self.ralphA.getY()+10,2)
        self.cameraB.setPos(self.ralphB.getX(),self.ralphB.getY()+10,2)
        
        #self.cameraA.setX(self.cameraA, 1 )
        #self.cameraB.setX(self.cameraB, 1 )
        
        # We will detect the height of the terrain by creating a collision
        # ray and casting it downward toward the terrain.  One ray will
        # start above ralph's head, and the other will start above the camera.
        # A ray may hit the terrain, or it may hit a rock or a tree.  If it
        # hits the terrain, we can detect the height.  If it hits anything
        # else, we rule that the move is illegal.

        self.cTrav = CollisionTraverser()

        self.ralphGroundRay = CollisionRay()
        self.ralphGroundRay.setOrigin(0,0,1000)
        self.ralphGroundRay.setDirection(0,0,-1)
        self.ralphGroundCol = CollisionNode('ralphRay')
        self.ralphGroundCol.addSolid(self.ralphGroundRay)
        self.ralphGroundCol.setFromCollideMask(BitMask32.bit(0))
        self.ralphGroundCol.setIntoCollideMask(BitMask32.allOff())
        self.ralphGroundColNpA = self.ralphA.attachNewNode(self.ralphGroundCol)
        self.ralphGroundColNpB = self.ralphB.attachNewNode(self.ralphGroundCol)
        self.ralphGroundHandlerA = CollisionHandlerQueue()
        self.ralphGroundHandlerB = CollisionHandlerQueue()
        self.cTrav.addCollider(self.ralphGroundColNpA, self.ralphGroundHandlerA)
        self.cTrav.addCollider(self.ralphGroundColNpB, self.ralphGroundHandlerB)

        self.camGroundRay = CollisionRay()
        self.camGroundRay.setOrigin(0,0,1000)
        self.camGroundRay.setDirection(0,0,-1)
        self.camGroundCol = CollisionNode('camRay')
        self.camGroundCol.addSolid(self.camGroundRay)
        self.camGroundCol.setFromCollideMask(BitMask32.bit(0))
        self.camGroundCol.setIntoCollideMask(BitMask32.allOff())
        self.camGroundColNpA = self.cameraA.attachNewNode(self.camGroundCol)
        self.camGroundColNpB = self.cameraB.attachNewNode(self.camGroundCol)
        self.camGroundHandlerA = CollisionHandlerQueue()
        self.camGroundHandlerB = CollisionHandlerQueue()
        self.cTrav.addCollider(self.camGroundColNpA, self.camGroundHandlerA)
        self.cTrav.addCollider(self.camGroundColNpB, self.camGroundHandlerB)

        # Uncomment this line to see the collision rays
        #self.ralphGroundColNp.show()
        #self.camGroundColNp.show()
       
        # Uncomment this line to show a visual representation of the 
        #collisions occuring
        #self.cTrav.showCollisions(render)
        
        # Create some lighting
        ambientLight = AmbientLight("ambientLight")
        ambientLight.setColor(Vec4(.3, .3, .3, 1))
        directionalLight = DirectionalLight("directionalLight")
        directionalLight.setDirection(Vec3(-5, -5, -5))
        directionalLight.setColor(Vec4(1, 1, 1, 1))
        directionalLight.setSpecularColor(Vec4(1, 1, 1, 1))
        render.setLight(render.attachNewNode(ambientLight))
        render.setLight(render.attachNewNode(directionalLight))
        
        self.headlightLeft = Spotlight ('headlightLeft')
        self.headlightLeft.setColor(VBase4(1,1,1,1))
        self.headlightLeft.getLens().setFov( 16, 16 )
        self.lens1 = PerspectiveLens()
        self.headlightLeft.setLens(self.lens1)
        self.hdlnp = self.ralphA.attachNewNode(self.headlightLeft)        
        render.setLight(self.hdlnp)
        
        self.headlightRight = Spotlight ('headlightRight')
        self.headlightRight.setColor(VBase4(1,1,1,1))
        self.headlightRight.getLens().setFov( 16, 16 )
        self.lens1 = PerspectiveLens()
        self.headlightRight.setLens(self.lens1)
        self.hdrnp = self.ralphB.attachNewNode(self.headlightRight)        
        render.setLight(self.hdrnp)
        #self.lightAttr = LightAttrib.makeAllOff()
        
        
        base.cTrav = CollisionTraverser()
        #sets the collision handler to one that generates collision events
        self.cHandler = CollisionHandlerEvent()
        #set the pattern for the event sent on collision
        #"%in" is substituted with the name of the "into" object
        self.cHandler.setInPattern("hit-%in")
        
        self.bullets = []
        
        self.ralphADead = False
        self.ralphBDead = False
        self.count = 0
    
    #Records the state of the arrow keys
    def setKey(self, key, value):
        self.keyMap[key] = value
        
    def hit(self, cEntry):
        self.bullets.remove(cEntry.getIntoNodePath().getParent())
        cEntry.getIntoNodePath().getParent().remove()
        if(cEntry.getFromNodePath() == self.ralphA):
            ralphAHealth -= 1
        if(cEntry.getFromNodePath() == self.ralphB):
            ralphBHealth -= 1
            
        if (ralphAHealth == 0):
            self.ralphA.stop()
            self.ralphB.play("death")
            self.ralphADead = True
        if (ralphBHealth == 0):
            self.ralphB.stop()
            self.ralphB.play("death")
            self.ralphBDead = True
        
    
    def createCamera(self,disp):
        camera = base.makeCamera(base.win,displayRegion = disp)
        camera.node().getLens().setAspectRatio(3.0/4.0)
        camera.node().getLens().setFov(45)
        return camera
    
    # Accepts arrow keys to move either the player or the menu cursor,
    # Also deals with grid checking and collision detection
    def move(self, task):
    
        # save ralph's initial position so that we can restore it,
        # in case he falls off the map or runs into something.
        if (self.ralphADead or self.ralphBDead):
            self.count += 1
            if (count == 100):
                sys.exit
            

        startposA = self.ralphA.getPos()
        startposB = self.ralphB.getPos()

        # If a move-key is pressed, move ralph in the specified direction.

        if (self.keyMap["leftA"]!=0):
            self.ralphA.setH(self.ralphA.getH() + 100 * globalClock.getDt())
        if (self.keyMap["rightA"]!=0):
            self.ralphA.setH(self.ralphA.getH() - 100 * globalClock.getDt())
        if (self.keyMap["forwardA"]!=0):
            self.ralphA.setY(self.ralphA, 75 * globalClock.getDt())
        if (self.keyMap["brakeA"]!=0):
            self.ralphA.setY(self.ralphA, -75 * globalClock.getDt())
            
        if (self.keyMap["leftB"]!=0):
            self.ralphB.setH(self.ralphB.getH() + 100 * globalClock.getDt())
        if (self.keyMap["rightB"]!=0):
            self.ralphB.setH(self.ralphB.getH() - 100 * globalClock.getDt())
        if (self.keyMap["forwardB"]!=0):
            self.ralphB.setY(self.ralphB, 75 * globalClock.getDt())
        if (self.keyMap["brakeB"]!=0):
            self.ralphB.setY(self.ralphB, -75 * globalClock.getDt())
            

        # If ralph is moving, loop the run animation.
        # If he is standing still, stop the animation.

        if (self.keyMap["forwardA"]!=0):
            if self.isMovingA is False:
                self.ralphA.loop("accelerate")
                self.isMovingA = True
                self.sounds.playSound('acc1')
            else:
                self.sounds.loopSound('max1',True)
                self.sounds.playSound('max1')
                
                
        elif  (self.keyMap["leftA"]!=0):
                self.ralphA.play("turn_left")
                
        elif (self.keyMap["rightA"]!=0):
                self.ralphA.play("turn_right")
                
        else:
            if self.isMovingA:
                self.sounds.loopSound('max1',False)
                self.sounds.stopSound('acc1')
                self.sounds.stopSound('max1')
                self.sounds.playSound('dec1')
                self.ralphA.stop()
                self.ralphA.pose("accelerate",1)
                self.isMovingA = False
                
        if (self.keyMap["forwardB"]!=0):
            if self.isMovingB is False:
                self.ralphB.loop("accelerate")
                self.isMovingB = True
                self.sounds.playSound('acc3')
            else:
                self.sounds.loopSound('max3',True)
                self.sounds.playSound('max3')
                
                
        elif  (self.keyMap["leftB"]!=0):
                self.ralphB.play("turn_left")
                
        elif (self.keyMap["rightB"]!=0):
                self.ralphB.play("turn_right")
                
        else:
            if self.isMovingB:
                self.sounds.loopSound('max3',False)
                self.sounds.stopSound('acc3')
                self.sounds.stopSound('max3')
                self.sounds.playSound('dec3')
                self.ralphB.stop()
                self.ralphB.pose("accelerate",1)
                self.isMovingB = False
                
                

        # If the camera is too far from ralph, move it closer.
        # If the camera is too close to ralph, move it farther.

        camvecA = self.ralphA.getPos() - self.cameraA.getPos()
        camvecA.setZ(0)
        camdistA = camvecA.length()
        camvecA.normalize()
        camvecB = self.ralphB.getPos() - self.cameraB.getPos()
        camvecB.setZ(0)
        camdistB = camvecB.length()
        camvecB.normalize()
        
        if (camdistA > 10.0):
            self.cameraA.setPos(self.cameraA.getPos() + camvecA*(camdistA-10))
            camdistA = 10.0
        if (camdistA < 5.0):
            self.cameraA.setPos(self.cameraA.getPos() - camvecA*(5-camdistA))
            camdistA = 5.0
        if (camdistB > 10.0):
            self.cameraB.setPos(self.cameraB.getPos() + camvecB*(camdistB-10))
            camdistB = 10.0
        if (camdistB < 5.0):
            self.cameraB.setPos(self.cameraB.getPos() - camvecB*(5-camdistB))
            camdistB = 5.0
            
        # Now check for collisions.

        self.cTrav.traverse(render)

        # Adjust ralph's Z coordinate.  If ralph's ray hit terrain,
        # update his Z. If it hit anything else, or didn't hit anything, put
        # him back where he was last frame.

        entriesA = []
        for i in range(self.ralphGroundHandlerA.getNumEntries()):
            entry = self.ralphGroundHandlerA.getEntry(i)
            entriesA.append(entry)
        entriesA.sort(lambda x,y: cmp(y.getSurfacePoint(render).getZ(),
                                     x.getSurfacePoint(render).getZ()))
        if (len(entriesA)>0) and (entriesA[0].getIntoNode().getName() == "terrain"):
            self.ralphA.setZ(entriesA[0].getSurfacePoint(render).getZ() + .75)
        else:
            self.ralphA.setPos(startposA + .75)
            
        entriesB = []
        for i in range(self.ralphGroundHandlerB.getNumEntries()):
            entry = self.ralphGroundHandlerB.getEntry(i)
            entriesB.append(entry)
        entriesB.sort(lambda x,y: cmp(y.getSurfacePoint(render).getZ(),
                                     x.getSurfacePoint(render).getZ()))
        if (len(entriesB)>0) and (entriesB[0].getIntoNode().getName() == "terrain"):
            self.ralphB.setZ(entriesB[0].getSurfacePoint(render).getZ() + .75)
        else:
            self.ralphB.setPos(startposB + .75)

        # Keep the camera at one foot above the terrain,
        # or two feet above ralph, whichever is greater.
        
        entriesA = []
        for i in range(self.camGroundHandlerA.getNumEntries()):
            entry = self.camGroundHandlerA.getEntry(i)
            entriesA.append(entry)
        entriesA.sort(lambda x,y: cmp(y.getSurfacePoint(render).getZ(),
                                     x.getSurfacePoint(render).getZ()))
        if (len(entriesA)>0) and (entriesA[0].getIntoNode().getName() == "terrain"):
            self.cameraA.setZ(entriesA[0].getSurfacePoint(render).getZ()+1.0)
        if (self.cameraA.getZ() < self.ralphA.getZ() + 5.0):
            self.cameraA.setZ(self.ralphA.getZ() + 5.0)
            
        entriesB = []
        for i in range(self.camGroundHandlerB.getNumEntries()):
            entry = self.camGroundHandlerB.getEntry(i)
            entriesB.append(entry)
        entriesB.sort(lambda x,y: cmp(y.getSurfacePoint(render).getZ(),
                                     x.getSurfacePoint(render).getZ()))
        if (len(entriesB)>0) and (entriesB[0].getIntoNode().getName() == "terrain"):
            self.cameraB.setZ(entriesB[0].getSurfacePoint(render).getZ()+1.0)
        if (self.cameraB.getZ() < self.ralphB.getZ() + 5.0):
            self.cameraB.setZ(self.ralphB.getZ() + 5.0)
            
        self.cameraA.setY(self.ralphA.getY() -100)
        self.cameraA.setX(self.ralphA.getX() +110)
        
        # The camera should look in ralph's direction,
        # but it should also try to stay horizontal, so look at
        # a floater which hovers above ralph's head.
        
        self.floaterA.setPos(self.ralphA.getPos())
        self.floaterA.setZ(self.ralphA.getZ() + 3.0)
        self.cameraA.lookAt(self.floaterA)
        
        self.floaterB.setPos(self.ralphB.getPos())
        self.floaterB.setZ(self.ralphB.getZ() + 3.0)
        self.cameraB.lookAt(self.floaterB)
        
        for i in range(len(self.bullets)):
            bullet = self.bullets[i]
            bullet.setPos(bullet, 0, 10 * globalClock.getDt(),0)
            
        if (self.keyMap["fireA"]!=0):
            bullet = loader.loadModel("models/bullet")
            bullet.setScale(.5)
            temp = list(self.ralphA.getPos())
            temp[1] += 2
            position = tuple(temp)
            bullet.setPos(position)
            bullet.setH(self.ralphA.getH())
            self.bullets.append(bullet)
            bullet.reparentTo(render)
            cSphere = CollisionSphere((0,0,0), .5)
            cNode = CollisionNode("bullet")
            cNode.addSolid(cSphere)
            cNodePath = bullet.attachNewNode(cNode)
            
        if (self.keyMap["fireB"]!=0):
            bullet = loader.loadModel("models/bullet")
            bullet.setScale(.5)
            temp = list(self.ralphB.getPos())
            temp[1] += 2
            position = tuple(temp)
            bullet.setPos(position)
            bullet.setH(self.ralphB.getH())
            self.bullets.append(bullet)
            bullet.reparentTo(render)
            cSphere = CollisionSphere((0,0,0), .5)
            cNode = CollisionNode("bullet")
            cNode.addSolid(cSphere)
            cNodePath = bullet.attachNewNode(cNode)
            #cNodePath.show()

        return task.cont


w = World()
run()

